package com.xiang.ad.search.impl;

import com.alibaba.fastjson.JSON;
import com.xiang.ad.index.CommonStatus;
import com.xiang.ad.index.DataTable;
import com.xiang.ad.index.adunit.AdUnitIndex;
import com.xiang.ad.index.adunit.AdUnitObject;
import com.xiang.ad.index.creative.CreativeIndex;
import com.xiang.ad.index.creative.CreativeObject;
import com.xiang.ad.index.creativeunit.CreativeUnitIndex;
import com.xiang.ad.index.district.UnitDistrictIndex;
import com.xiang.ad.index.interest.UnitItIndex;
import com.xiang.ad.index.keyword.UnitKeywordIndex;
import com.xiang.ad.search.ISearch;
import com.xiang.ad.search.vo.SearchRequest;
import com.xiang.ad.search.vo.SearchResponse;
import com.xiang.ad.search.vo.feature.DistrictFeature;
import com.xiang.ad.search.vo.feature.FeatureRelation;
import com.xiang.ad.search.vo.feature.ItFeature;
import com.xiang.ad.search.vo.feature.KeywordFeature;
import com.xiang.ad.search.vo.media.AdSlot;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;

import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

/**
 * Created by xiang.
 * 广告主请求的 实现类
 * 根据请求信息，获取到对应的广告数据
 * 1、构造响应对象
 * 2、初次筛选：根据流量类型 获取 adUnit
 * 3、再次筛选：根据feature类型 获取 adUnit
 * 4、填充响应对象
 * 5、
 */
@Slf4j
@Service
public class SearchImpl implements ISearch {

    //错误回退的方法  第二个参数是异常传入的方法
    public SearchResponse fallback(SearchRequest request, Throwable e) {
        return null;
    }

    //
    @Override
    @HystrixCommand(fallbackMethod = "fallback")  //错误回退的方法，必须定义在当前类中
    public SearchResponse fetchAds(SearchRequest request) {

        // 请求的广告位信息 取出来
        List<AdSlot> adSlots = request.getRequestInfo().getAdSlots();

        // 三种 Feature---关键词、地域、兴趣
        KeywordFeature keywordFeature =
                request.getFeatureInfo().getKeywordFeature();
        DistrictFeature districtFeature =
                request.getFeatureInfo().getDistrictFeature();
        ItFeature itFeature =
                request.getFeatureInfo().getItFeature();

        FeatureRelation relation = request.getFeatureInfo().getRelation();

        // 构造响应对象
        SearchResponse response = new SearchResponse();
        Map<String, List<SearchResponse.Creative>> adSlot2Ads =
                response.getAdSlot2Ads();

        //广告检索，检索的根本是推广单元
        for (AdSlot adSlot : adSlots) {

            //用于存储 id
            Set<Long> targetUnitIdSet;

            // 根据流量类型获取初始 AdUnit（预筛选）
            Set<Long> adUnitIdSet = DataTable.of(
                    AdUnitIndex.class
            ).match(adSlot.getPositionType());

            if (relation == FeatureRelation.AND) {

                filterKeywordFeature(adUnitIdSet, keywordFeature);
                filterDistrictFeature(adUnitIdSet, districtFeature);
                filterItTagFeature(adUnitIdSet, itFeature);

                targetUnitIdSet = adUnitIdSet;

            } else {
                targetUnitIdSet = getORRelationUnitIds(
                        adUnitIdSet,
                        keywordFeature,
                        districtFeature,
                        itFeature
                );
            }

            //根据推广单元的ids获取到对应的索引对象
            List<AdUnitObject> unitObjects =
                    DataTable.of(AdUnitIndex.class).fetch(targetUnitIdSet);//

            //通过推广单元和计划的 状态 ，如果是无效就不用进行处理了
            filterAdUnitAndPlanStatus(unitObjects, CommonStatus.VALID);

            /**
             * 通过推广单元获取关联的创意实现
             * 创意与推广单元存在着多对多的关系
             * 1、可以通过creativeUnitIndex索引服务以及creativeUnitObject去获取到推广单元所有关联的创意的id
             * 2、再通过creativeIndex通过id再获取到对应的CreativeObject
             *
             * 这里真正的调用
             */
            List<Long> adIds = DataTable.of(CreativeUnitIndex.class)//第一步
                    .selectAds(unitObjects);
            List<CreativeObject> creatives = DataTable.of(CreativeIndex.class)//第二步
                    .fetch(adIds);//根据推广单元的ids获取到对应的索引对象

            //这里就已经获取到了创意对象了，但是还需要最终的过滤（我自己想的，无所谓的）


            // 通过 AdSlot 实现对 CreativeObject 的过滤
            // 因为adSlot中包含了对广告位的一些信息，高宽啥的，需要跟广告位进行匹配，不合适肯定就不行
            filterCreativeByAdSlot(
                    creatives,
                    adSlot.getWidth(),
                    adSlot.getHeight(),
                    adSlot.getType()
            );

            // 全部过滤完之后，进行adSlot2Ads的填充
            adSlot2Ads.put(
                    //
                    adSlot.getAdSlotCode(), buildCreativeResponse(creatives)
            );
        }

        log.info("fetchAds: {}-{}",
                JSON.toJSONString(request),
                JSON.toJSONString(response));

        //返回响应对象
        return response;
    }

    //
    private Set<Long> getORRelationUnitIds(Set<Long> adUnitIdSet,
                                           KeywordFeature keywordFeature,
                                           DistrictFeature districtFeature,
                                           ItFeature itFeature) {

        if (CollectionUtils.isEmpty(adUnitIdSet)) {
            return Collections.emptySet();
        }

        Set<Long> keywordUnitIdSet = new HashSet<>(adUnitIdSet);
        Set<Long> districtUnitIdSet = new HashSet<>(adUnitIdSet);
        Set<Long> itUnitIdSet = new HashSet<>(adUnitIdSet);

        //过滤方法的调用，将不符合 这三种约束的 就过滤掉了
        filterKeywordFeature(keywordUnitIdSet, keywordFeature);
        filterDistrictFeature(districtUnitIdSet, districtFeature);
        filterItTagFeature(itUnitIdSet, itFeature);

        return new HashSet<>(
                CollectionUtils.union(
                        CollectionUtils.union(keywordUnitIdSet, districtUnitIdSet),
                        itUnitIdSet
                )
        );
    }

    // 通过CollectionUtils的filter方法，调用各自的match方法，进行匹配过滤
    // 根据关键词实现对推广单元的再次筛选过滤
    private void filterKeywordFeature(
            Collection<Long> adUnitIds, KeywordFeature keywordFeature) {

        if (CollectionUtils.isEmpty(adUnitIds)) {
            return;
        }

        if (CollectionUtils.isNotEmpty(keywordFeature.getKeywords())) {

            //两个参数，进行过滤，根据feature的match方法进行匹配过滤
            CollectionUtils.filter(
                    adUnitIds,//需要过滤的集合
                    //判断条件
                    adUnitId ->
                            DataTable.of(UnitKeywordIndex.class)
                                    .match(adUnitId,
                                            keywordFeature.getKeywords())
            );
        }
    }

    //根据地域实现对推广单元的再次筛选过滤
    private void filterDistrictFeature(
            Collection<Long> adUnitIds, DistrictFeature districtFeature
    ) {
        if (CollectionUtils.isEmpty(adUnitIds)) {
            return;
        }

        if (CollectionUtils.isNotEmpty(districtFeature.getDistricts())) {

            //两个参数，进行过滤，根据feature的match方法进行匹配过滤
            CollectionUtils.filter(
                    adUnitIds,
                    adUnitId ->
                            DataTable.of(UnitDistrictIndex.class)
                                    .match(adUnitId,
                                            districtFeature.getDistricts())
            );
        }
    }

    //根据兴趣实现对推广单元的再次筛选过滤
    private void filterItTagFeature(Collection<Long> adUnitIds,
                                    ItFeature itFeature) {

        if (CollectionUtils.isEmpty(adUnitIds)) {
            return;
        }

        if (CollectionUtils.isNotEmpty(itFeature.getIts())) {

            //两个参数，进行过滤，根据feature的match方法进行匹配过滤
            CollectionUtils.filter(
                    adUnitIds,
                    adUnitId ->
                            DataTable.of(UnitItIndex.class)
                                    .match(adUnitId,
                                            itFeature.getIts())
            );
        }
    }

    //实现对推广单元及计划 状态的过滤，无效的就不用处理了
    private void filterAdUnitAndPlanStatus(List<AdUnitObject> unitObjects,
                                           CommonStatus status) {

        if (CollectionUtils.isEmpty(unitObjects)) {
            return;
        }

        //第一个参数是想要过滤的集合类型，第二个是逻辑
        CollectionUtils.filter(
                unitObjects,
                //推广单元 & 推广计划 都是有效的才能通过过滤
                object -> object.getUnitStatus().equals(status.getStatus())
                && object.getAdPlanObject().getPlanStatus().equals(status.getStatus())
        );
    }

    //根据 媒体的基本信息---广告位的信息  过滤创意
    private void filterCreativeByAdSlot(List<CreativeObject> creatives,
                                        Integer width,//宽
                                        Integer height,//高
                                        List<Integer> type ) {//类型

        //判空处理
        if (CollectionUtils.isEmpty(creatives)) {
            return;
        }

        //根据广告位的字段进行过滤
        CollectionUtils.filter(
                creatives,//想要过滤的集合对象
                creative ->
                        //第一个判断条件：审核状态
                        creative.getAuditStatus().equals(CommonStatus.VALID.getStatus())
                                //宽度是否符合
                && creative.getWidth().equals(width)
                                //高度是否符合
                && creative.getHeight().equals(height)
                                //type是否被包含，包含就行
                && type.contains(creative.getType())
        );
    }

    // 构建响应（返回创意信息） 这里设计就是 一个广告位，对应一个广告创意，所以不是多个，可以以后再加list的方式
    // 实现CreativeObject 转换为 SearchResponse中的Creative
    private List<SearchResponse.Creative> buildCreativeResponse(
            List<CreativeObject> creatives
    ) {

        //判空校验
        if (CollectionUtils.isEmpty(creatives)) {
            return Collections.emptyList();
        }

        //可以考虑优先级排序的方式获取，我这里不知道咋搞那就随机 吧
        //随机creatives中的一个创意对象
        CreativeObject randomObject = creatives.get(
                Math.abs(new Random().nextInt()) % creatives.size()
        );


        //返回 构造广告创意的响应的结果 就是 Creative 创意对象
        return Collections.singletonList( //随机的返回一个创意对象，为了测试方便

                //将索引对象 转换为 定义的返回给用户的对象
                SearchResponse.convert(randomObject)
        );
    }
}
